/*
    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/
/*

Primitives for Arrays.

*/

#include "GC.h"
#include "PyrKernel.h"
#include "PyrPrimitive.h"
#include "SCBase.h"
#include "SC_InlineBinaryOp.h"
#include "SC_Constants.h"
#include "SC_Levenshtein.h"
#include "SC_Hungarian.h" // header-only; namespace schungarian::solve<T>(matrix)

#include <cstring>
#include <vector>

// Primitives that work with Arrays. Most of these are used in ArrayedCollection and Array.

int basicSize(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot* a;
    PyrObject* obj;

    a = g->sp;
    if (NotObj(a)) {
        SetInt(a, 0);
        return errNone;
    }
    obj = slotRawObject(a);
    SetInt(a, obj->size);
    return errNone;
}

int basicMaxSize(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot* a;
    PyrObject* obj;
    int maxsize;

    a = g->sp;
    if (NotObj(a)) {
        SetInt(a, 0);
        return errNone;
    }
    obj = slotRawObject(a);
    maxsize = MAXINDEXSIZE(obj);
    SetInt(a, maxsize);
    return errNone;
}

int basicSwap(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c, tempi, tempj;
    int i, j;
    PyrObject* obj;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    if (NotObj(a))
        return errWrongType;
    if (NotInt(b))
        return errIndexNotAnInteger;
    if (NotInt(c))
        return errIndexNotAnInteger;
    obj = slotRawObject(a);
    if (obj->IsImmutable())
        return errImmutableObject;
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;
    i = slotRawInt(b);
    j = slotRawInt(c);
    if (i < 0 || i >= obj->size)
        return errIndexOutOfRange;
    if (j < 0 || j >= obj->size)
        return errIndexOutOfRange;
    getIndexedSlot(obj, &tempi, i);
    getIndexedSlot(obj, &tempj, j);
    putIndexedSlot(g, obj, &tempi, j);
    putIndexedSlot(g, obj, &tempj, i);
    // in case it is partial scan obj
    g->gc->GCWrite(obj, &tempi);
    g->gc->GCWrite(obj, &tempj);

    return errNone;
}

int getIndexedInt(PyrObject* obj, int index, int* value);
void DumpBackTrace(VMGlobals* g);

int basicAt(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int index;
    PyrObject* obj;

    a = g->sp - 1;
    b = g->sp;

    if (NotObj(a))
        return errWrongType;
    obj = slotRawObject(a);
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    int err = slotIntVal(b, &index);
    if (!err) {
        if (index < 0 || index >= obj->size) {
            slotCopy(a, &o_nil);
        } else {
            getIndexedSlot(obj, a, index);
        }
    } else if (isKindOfSlot(b, class_arrayed_collection)) {
        PyrObject* indexArray = slotRawObject(b);
        int size = indexArray->size;
        PyrObject* outArray = newPyrArray(g->gc, size, 0, true);
        PyrSlot* outArraySlots = outArray->slots;
        for (int i = 0; i < size; ++i) {
            int err = getIndexedInt(indexArray, i, &index);
            if (err)
                return err;
            if (index < 0 || index >= obj->size) {
                slotCopy(&outArraySlots[i], &o_nil);
            } else {
                getIndexedSlot(obj, outArraySlots + i, index);
            }
        }
        outArray->size = size;
        SetObject(a, outArray);
    } else {
        return errIndexNotAnInteger;
    }
    return errNone;
}

int basicRemoveAt(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int index, length, elemsize;
    PyrObject* obj;
    void* ptr;

    a = g->sp - 1;
    b = g->sp;

    if (NotObj(a))
        return errWrongType;
    int err = slotIntVal(b, &index);
    if (err)
        return errWrongType;

    obj = slotRawObject(a);
    if (obj->IsImmutable())
        return errImmutableObject;
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (index < 0 || index >= obj->size)
        return errIndexOutOfRange;
    switch (obj->obj_format) {
    default:
    case obj_slot:
        ptr = obj->slots + index;
        slotCopy(a, (PyrSlot*)ptr);
        break;
    case obj_double:
        ptr = obj->slots + index;
        SetFloat(a, *(double*)ptr);
        break;
    case obj_float:
        ptr = ((float*)(obj->slots)) + index;
        SetFloat(a, *(float*)ptr);
        break;
    case obj_int32:
        ptr = ((int32*)(obj->slots)) + index;
        SetInt(a, *(int32*)ptr);
        break;
    case obj_int16:
        ptr = ((int16*)(obj->slots)) + index;
        SetInt(a, *(int16*)ptr);
        break;
    case obj_int8:
        ptr = ((int8*)(obj->slots)) + index;
        SetInt(a, *(int8*)ptr);
        break;
    case obj_symbol:
        ptr = ((int*)(obj->slots)) + index;
        SetSymbol(a, *(PyrSymbol**)ptr);
        break;
    case obj_char:
        ptr = ((unsigned char*)(obj->slots)) + index;
        SetChar(a, *(unsigned char*)ptr);
        break;
    }
    length = obj->size - index - 1;
    if (length > 0) {
        elemsize = gFormatElemSize[obj->obj_format];
        memmove(ptr, (char*)ptr + elemsize, length * elemsize);
        if (obj->obj_format <= obj_slot) {
            // might be partial scan object
            g->gc->GCWrite(obj, obj->slots + index);
        }
    }
    obj->size--;
    return errNone;
}


int basicTakeAt(struct VMGlobals* g, int numArgsPushed);
int basicTakeAt(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int index, lastIndex;
    PyrObject* obj;

    a = g->sp - 1;
    b = g->sp;

    if (NotObj(a))
        return errWrongType;
    int err = slotIntVal(b, &index);
    if (err)
        return errWrongType;

    obj = slotRawObject(a);
    if (obj->IsImmutable())
        return errImmutableObject;
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    lastIndex = obj->size - 1;
    if (index < 0 || index >= obj->size)
        return errIndexOutOfRange;
    switch (obj->obj_format) {
    case obj_slot: {
        PyrSlot* ptr = obj->slots + index;
        PyrSlot* lastptr = obj->slots + lastIndex;
        slotCopy(a, ptr);
        *ptr = *lastptr;
        // might be partial scan obj
        g->gc->GCWrite(obj, ptr);
    } break;
    case obj_double: {
        PyrSlot* ptr = obj->slots + index;
        PyrSlot* lastptr = obj->slots + lastIndex;
        SetFloat(a, *(double*)ptr);
        *ptr = *lastptr;
        // might be partial scan obj
        g->gc->GCWrite(obj, ptr);
    } break;
    case obj_float: {
        float* ptr = ((float*)(obj->slots)) + index;
        float* lastptr = ((float*)(obj->slots)) + lastIndex;
        SetFloat(a, *(float*)ptr);
        *ptr = *lastptr;
    } break;
    case obj_int32: {
        int32* ptr = ((int32*)(obj->slots)) + index;
        int32* lastptr = ((int32*)(obj->slots)) + lastIndex;
        SetInt(a, *(int32*)ptr);
        *ptr = *lastptr;
    } break;
    case obj_int16: {
        int16* ptr = ((int16*)(obj->slots)) + index;
        int16* lastptr = ((int16*)(obj->slots)) + lastIndex;
        SetInt(a, *(int16*)ptr);
        *ptr = *lastptr;
    } break;
    case obj_int8: {
        int8* ptr = ((int8*)(obj->slots)) + index;
        int8* lastptr = ((int8*)(obj->slots)) + lastIndex;
        SetInt(a, *(int8*)ptr);
        *ptr = *lastptr;
    } break;
    case obj_symbol: {
        int32* ptr = ((int32*)(obj->slots)) + index;
        int32* lastptr = ((int32*)(obj->slots)) + lastIndex;
        SetSymbol(a, *(PyrSymbol**)ptr);
        *ptr = *lastptr;
    } break;
    case obj_char: {
        unsigned char* ptr = ((unsigned char*)(obj->slots)) + index;
        unsigned char* lastptr = ((unsigned char*)(obj->slots)) + lastIndex;
        SetChar(a, *(unsigned char*)ptr);
        *ptr = *lastptr;
    } break;
    }
    obj->size--;
    return errNone;
}

int basicWrapAt(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int index;
    PyrObject* obj;
    a = g->sp - 1;
    b = g->sp;

    if (NotObj(a))
        return errWrongType;
    obj = slotRawObject(a);
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (obj->size == 0) {
        SetNil(a);
        return errNone;
    }

    int err = slotIntVal(b, &index);

    if (!err) {
        index = sc_mod((int)index, (int)obj->size);
        getIndexedSlot(obj, a, index);
    } else if (isKindOfSlot(b, class_arrayed_collection)) {
        PyrObject* indexArray = slotRawObject(b);
        int size = indexArray->size;
        PyrObject* outArray = newPyrArray(g->gc, size, 0, true);
        PyrSlot* outArraySlots = outArray->slots;
        for (int i = 0; i < size; ++i) {
            int err = getIndexedInt(indexArray, i, &index);
            if (err)
                return err;
            index = sc_mod((int)index, (int)obj->size);
            getIndexedSlot(obj, outArraySlots + i, index);
        }
        outArray->size = size;
        SetObject(a, outArray);
    } else
        return errIndexNotAnInteger;

    return errNone;
}

int basicFoldAt(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int index;
    PyrObject* obj;
    a = g->sp - 1;
    b = g->sp;

    if (NotObj(a))
        return errWrongType;
    obj = slotRawObject(a);
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (obj->size == 0) {
        SetNil(a);
        return errNone;
    }

    int err = slotIntVal(b, &index);

    if (!err) {
        index = sc_fold(index, 0, obj->size - 1);
        getIndexedSlot(obj, a, index);
    } else if (isKindOfSlot(b, class_arrayed_collection)) {
        PyrObject* indexArray = slotRawObject(b);
        int size = indexArray->size;
        PyrObject* outArray = newPyrArray(g->gc, size, 0, true);
        PyrSlot* outArraySlots = outArray->slots;
        for (int i = 0; i < size; ++i) {
            int err = getIndexedInt(indexArray, i, &index);
            if (err)
                return err;
            index = sc_fold(index, 0, obj->size - 1);
            getIndexedSlot(obj, outArraySlots + i, index);
        }
        outArray->size = size;
        SetObject(a, outArray);
    } else
        return errIndexNotAnInteger;

    return errNone;
}

int basicClipAt(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int index;
    PyrObject* obj;
    a = g->sp - 1;
    b = g->sp;

    if (NotObj(a))
        return errWrongType;
    obj = slotRawObject(a);
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (obj->size == 0) {
        SetNil(a);
        return errNone;
    }

    int err = slotIntVal(b, &index);

    if (!err) {
        index = sc_clip(index, 0, obj->size - 1);
        getIndexedSlot(obj, a, index);
    } else if (isKindOfSlot(b, class_arrayed_collection)) {
        PyrObject* indexArray = slotRawObject(b);
        int size = indexArray->size;
        PyrObject* outArray = newPyrArray(g->gc, size, 0, true);
        PyrSlot* outArraySlots = outArray->slots;
        for (int i = 0; i < size; ++i) {
            int err = getIndexedInt(indexArray, i, &index);
            if (err)
                return err;
            index = sc_clip(index, 0, obj->size - 1);
            getIndexedSlot(obj, outArraySlots + i, index);
        }
        outArray->size = size;
        SetObject(a, outArray);
    } else
        return errIndexNotAnInteger;

    return errNone;
}


int basicPut(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int index;
    PyrObject* obj;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    obj = slotRawObject(a);
    if (obj->IsImmutable())
        return errImmutableObject;
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (NotObj(a))
        return errWrongType;
    int err = slotIntVal(b, &index);

    if (!err) {
        if (index < 0 || index >= obj->size)
            return errIndexOutOfRange;
        return putIndexedSlot(g, obj, c, index);
    } else if (isKindOfSlot(b, class_arrayed_collection)) {
        PyrObject* indexArray = slotRawObject(b);
        int size = slotRawObject(b)->size;

        for (int i = 0; i < size; ++i) {
            int index;
            int err = getIndexedInt(indexArray, i, &index);
            if (err)
                return err;
            if (index < 0 || index >= obj->size)
                return errIndexOutOfRange;
            err = putIndexedSlot(g, obj, c, index);
            if (err)
                return err;
        }
        return errNone;
    } else
        return errIndexNotAnInteger;
}

int basicClipPut(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int index;
    PyrObject* obj;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    obj = slotRawObject(a);
    if (obj->IsImmutable())
        return errImmutableObject;
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (NotObj(a))
        return errWrongType;
    int err = slotIntVal(b, &index);

    if (!err) {
        index = sc_clip(index, 0, obj->size - 1);
        return putIndexedSlot(g, obj, c, index);
    } else if (isKindOfSlot(b, class_arrayed_collection)) {
        PyrObject* indexArray = slotRawObject(b);
        int size = slotRawObject(b)->size;

        for (int i = 0; i < size; ++i) {
            int index;
            int err = getIndexedInt(indexArray, i, &index);
            if (err)
                return err;
            index = sc_clip(index, 0, obj->size - 1);
            err = putIndexedSlot(g, obj, c, index);
            if (err)
                return err;
        }
        return errNone;
    } else
        return errIndexNotAnInteger;
}

int basicWrapPut(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int index;
    PyrObject* obj;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    obj = slotRawObject(a);
    if (obj->IsImmutable())
        return errImmutableObject;
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (NotObj(a))
        return errWrongType;
    int err = slotIntVal(b, &index);

    if (!err) {
        index = sc_mod((int)index, (int)obj->size);
        return putIndexedSlot(g, obj, c, index);
    } else if (isKindOfSlot(b, class_arrayed_collection)) {
        PyrObject* indexArray = slotRawObject(b);
        int size = slotRawObject(b)->size;

        for (int i = 0; i < size; ++i) {
            int index;
            int err = getIndexedInt(indexArray, i, &index);
            if (err)
                return err;
            index = sc_mod((int)index, (int)obj->size);
            err = putIndexedSlot(g, obj, c, index);
            if (err)
                return err;
        }
        return errNone;
    } else
        return errIndexNotAnInteger;
}

int basicFoldPut(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int index;
    PyrObject* obj;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    obj = slotRawObject(a);
    if (obj->IsImmutable())
        return errImmutableObject;
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (NotObj(a))
        return errWrongType;
    int err = slotIntVal(b, &index);

    if (!err) {
        index = sc_fold(index, 0, obj->size - 1);
        return putIndexedSlot(g, obj, c, index);
    } else if (isKindOfSlot(b, class_arrayed_collection)) {
        PyrObject* indexArray = slotRawObject(b);
        int size = slotRawObject(b)->size;

        for (int i = 0; i < size; ++i) {
            int index;
            int err = getIndexedInt(indexArray, i, &index);
            if (err)
                return err;
            index = sc_fold(index, 0, obj->size - 1);
            err = putIndexedSlot(g, obj, c, index);
            if (err)
                return err;
        }
        return errNone;
    } else
        return errIndexNotAnInteger;
}

int prArrayPutEach(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    PyrObject* obj;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    obj = slotRawObject(a);
    if (obj->IsImmutable())
        return errImmutableObject;
    if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (!isKindOfSlot(b, class_arrayed_collection))
        return errWrongType;
    if (!isKindOfSlot(c, class_arrayed_collection))
        return errWrongType;

    PyrSlot* indices = slotRawObject(b)->slots;
    PyrSlot* values = slotRawObject(c)->slots;
    int size = slotRawObject(b)->size;
    int valsize = slotRawObject(c)->size;

    for (int i = 0; i < size; ++i) {
        int index;
        int err = slotIntVal(indices + i, &index);
        if (err)
            return err;
        if (index < 0 || index >= obj->size)
            return errIndexOutOfRange;
        int valindex = sc_mod(i, valsize);
        err = putIndexedSlot(g, obj, values + valindex, index);
        if (err)
            return err;
    }

    return errNone;
}


int prArrayAssocAt(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    PyrObject* obj;
    bool found = false;

    a = g->sp - 1;
    b = g->sp;

    obj = slotRawObject(a);

    int size = obj->size;
    if (obj->obj_format == obj_slot) {
        PyrSlot* slots = obj->slots;
        for (int i = 0; i < size; i += 2) {
            if (SlotEq(slots + i, b)) {
                if (i + 1 >= size)
                    return errFailed;
                slotCopy(a, &slots[i + 1]);
                found = true;
                break;
            }
        }
    } else {
        PyrSlot slot;
        for (int i = 0; i < size; i += 2) {
            getIndexedSlot(obj, &slot, i);
            if (SlotEq(&slot, b)) {
                if (i + 1 >= size)
                    return errFailed;
                getIndexedSlot(obj, &slot, i + 1);
                slotCopy(a, &slot);
                found = true;
                break;
            }
        }
    }
    if (!found)
        SetNil(a);

    return errNone;
}


int prArrayAssocPut(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    PyrObject* obj;
    bool found = false;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    obj = slotRawObject(a);
    if (obj->IsImmutable())
        return errImmutableObject;

    int size = obj->size;
    if (obj->obj_format == obj_slot) {
        PyrSlot* slots = obj->slots;
        for (int i = 0; i < size; i += 2) {
            if (SlotEq(slots + i, b)) {
                if (i + 1 >= size)
                    return errFailed;
                slotCopy(&slots[i + 1], c);
                g->gc->GCWrite(obj, c);
                found = true;
                break;
            }
        }
    } else {
        PyrSlot slot;
        for (int i = 0; i < size; i += 2) {
            getIndexedSlot(obj, &slot, i);
            if (SlotEq(&slot, b)) {
                if (i + 1 >= size)
                    return errFailed;
                putIndexedSlot(g, obj, &slot, i + 1);
                g->gc->GCWrite(obj, c);
                found = true;
                break;
            }
        }
    }
    if (!found)
        SetNil(a);

    return errNone;
}

int prArrayIndexOf(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    PyrObject* obj;
    bool found = false;

    a = g->sp - 1;
    b = g->sp;

    obj = slotRawObject(a);

    int size = obj->size;
    if (obj->obj_format == obj_slot) {
        PyrSlot* slots = obj->slots;
        for (int i = 0; i < size; ++i) {
            if (SlotEq(slots + i, b)) {
                SetInt(a, i);
                found = true;
                break;
            }
        }
    } else {
        PyrSlot slot;
        for (int i = 0; i < size; ++i) {
            getIndexedSlot(obj, &slot, i);
            if (SlotEq(&slot, b)) {
                SetInt(a, i);
                found = true;
                break;
            }
        }
    }
    if (!found)
        SetNil(a);

    return errNone;
}


int prArrayPutSeries(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c, *d, *e;

    a = g->sp - 4;
    b = g->sp - 3;
    c = g->sp - 2;
    d = g->sp - 1;
    e = g->sp;

    PyrObject* inobj = slotRawObject(a);
    if (inobj->IsImmutable())
        return errImmutableObject;

    int size = inobj->size;

    if (NotInt(b) && NotNil(b))
        return errWrongType;
    if (NotInt(c) && NotNil(c))
        return errWrongType;
    if (NotInt(d) && NotNil(d))
        return errWrongType;

    int first = IsInt(b) ? slotRawInt(b) : 0;
    int last = IsInt(d) ? slotRawInt(d) : size - 1;
    int second = IsInt(c) ? slotRawInt(c) : (first < last ? first + 1 : first - 1);

    int step = second - first;

    first = sc_clip(first, 0, size - 1);
    last = sc_clip(last, 0, size - 1);

    int err = errNone;

    if (step == 0)
        return errFailed;
    if (step == 1) {
        for (int i = first; i <= last; ++i) {
            err = putIndexedSlot(g, inobj, e, i);
            if (err)
                return err;
        }
    } else if (step == -1) {
        for (int i = last; i >= first; --i) {
            err = putIndexedSlot(g, inobj, e, i);
            if (err)
                return err;
        }
    } else if (step > 0) {
        int length = (last - first) / step + 1;

        for (int i = first, j = 0; j < length; i += step, ++j) {
            err = putIndexedSlot(g, inobj, e, i);
            if (err)
                return err;
        }
    } else if (step < 0) {
        int length = (first - last) / -step + 1;

        for (int i = first, j = 0; j < length; i += step, ++j) {
            err = putIndexedSlot(g, inobj, e, i);
            if (err)
                return err;
        }
    }
    return errNone;
}


int prArrayAdd(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots;
    int maxelems, elemsize, format, tag, numbytes;
    int err, ival;
    double fval;

    a = g->sp - 1;
    b = g->sp;

    PyrObject* array = slotRawObject(a);
    if (array->IsImmutable())
        return errImmutableObject;

    format = slotRawObject(a)->obj_format;
    tag = gFormatElemTag[format];
    /*if (tag > 0) {
        if (GetTag(b) != tag) return errWrongType;
    } else if (tag == 0) {
        if (NotFloat(b)) return errWrongType;
    } // else format is obj_slot, any tag is acceptable*/
    elemsize = gFormatElemSize[format];
    maxelems = MAXINDEXSIZE(array);
    if (array->size >= maxelems || array->IsImmutable()) {
        numbytes = sizeof(PyrSlot) << (array->obj_sizeclass + 1);
        array = g->gc->New(numbytes, 0, format, true);
        array->classptr = slotRawObject(a)->classptr;
        array->size = slotRawObject(a)->size;
        memcpy(array->slots, slotRawObject(a)->slots, slotRawObject(a)->size * elemsize);
        SetRaw(a, array);
    }
    slots = array->slots;
    switch (format) {
    case obj_slot:
        slotCopy(&slots[array->size++], b);
        g->gc->GCWrite(array, b);
        break;
    case obj_int32:
        err = slotIntVal(b, &ival);
        if (err)
            return err;
        ((int32*)slots)[array->size++] = ival;
        break;
    case obj_int16:
        err = slotIntVal(b, &ival);
        if (err)
            return err;
        ((int16*)slots)[array->size++] = ival;
        break;
    case obj_int8:
        err = slotIntVal(b, &ival);
        if (err)
            return err;
        ((int8*)slots)[array->size++] = ival;
        break;
    case obj_char:
        if (NotChar(b))
            return errWrongType;
        ((char*)slots)[array->size++] = slotRawChar(b);
        break;
    case obj_symbol:
        if (NotSym(b))
            return errWrongType;
        ((PyrSymbol**)slots)[array->size++] = slotRawSymbol(b);
        break;
    case obj_float:
        err = slotDoubleVal(b, &fval);
        if (err)
            return err;
        ((float*)slots)[array->size++] = fval;
        break;
    case obj_double:
        err = slotDoubleVal(b, &fval);
        if (err)
            return err;
        ((double*)slots)[array->size++] = fval;
        break;
    }
    return errNone;
}

int prArrayInsert(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c, *slots1, *slots2;
    PyrObject *array, *oldarray;
    int err, ival;
    double fval;

    a = g->sp - 2; // array
    b = g->sp - 1; // index
    c = g->sp; // value
    if (NotInt(b))
        return errWrongType;

    array = slotRawObject(a);
    const int format = slotRawObject(a)->obj_format;

    const int size = array->size;
    int index = slotRawInt(b);
    index = sc_clip(index, 0, size);
    const int remain = size - index;

    const int elemsize = gFormatElemSize[format];
    const int maxelems = MAXINDEXSIZE(array);
    if (size + 1 > maxelems || array->IsImmutable()) {
        oldarray = array;

        const int numbytes = sizeof(PyrSlot) << (array->obj_sizeclass + 1);
        array = g->gc->New(numbytes, 0, format, true);

        array->classptr = oldarray->classptr;

        array->size = size + 1;
        SetRaw(a, array);
        slots1 = array->slots;
        slots2 = oldarray->slots;
        if (index)
            memcpy(slots1, slots2, index * elemsize);

        switch (format) {
        case obj_slot:

            slotCopy(&slots1[index], c);
            if (remain)
                memcpy(slots1 + index + 1, slots2 + index, remain * elemsize);
            if (!g->gc->ObjIsGrey(array))
                g->gc->ToGrey(array);
            break;
        case obj_int32:
            err = slotIntVal(c, &ival);
            if (err)
                return err;
            ((int32*)slots1)[index] = ival;
            if (remain) {
                memcpy((int*)slots1 + index + 1, (int*)slots2 + index, remain * elemsize);
            }
            break;
        case obj_int16:
            err = slotIntVal(c, &ival);
            if (err)
                return err;
            ((int16*)slots1)[index] = ival;
            if (remain) {
                memcpy((short*)slots1 + index + 1, (short*)slots2 + index, remain * elemsize);
            }
            break;
        case obj_int8:
            err = slotIntVal(c, &ival);
            if (err)
                return err;
            ((int8*)slots1)[index] = ival;
            if (remain) {
                memcpy((char*)slots1 + index + 1, (char*)slots2 + index, remain * elemsize);
            }
            break;
        case obj_char:
            if (NotChar(c))
                return errWrongType;
            ((char*)slots1)[index] = slotRawInt(c);
            if (remain) {
                memcpy((char*)slots1 + index + 1, (char*)slots2 + index, remain * elemsize);
            }
            break;
        case obj_symbol:
            if (NotSym(c))
                return errWrongType;
            ((PyrSymbol**)slots1)[index] = slotRawSymbol(c);
            if (remain) {
                memcpy((int*)slots1 + index + 1, (int*)slots2 + index, remain * elemsize);
            }
            break;
        case obj_float:
            err = slotDoubleVal(c, &fval);
            if (err)
                return err;
            ((float*)slots1)[index] = fval;
            if (remain) {
                memcpy((float*)slots1 + index + 1, (float*)slots2 + index, remain * elemsize);
            }
            break;
        case obj_double:
            err = slotDoubleVal(c, &fval);
            if (err)
                return err;
            ((double*)slots1)[index] = fval;
            if (remain) {
                memcpy((double*)slots1 + index + 1, (double*)slots2 + index, remain * elemsize);
            }
            break;
        }
    } else {
        array->size = size + 1;
        slots1 = array->slots;
        switch (format) {
        case obj_slot:
            if (remain)
                memmove(slots1 + index + 1, slots1 + index, remain * elemsize);
            slotCopy(&slots1[index], c);
            if (!g->gc->ObjIsGrey(array))
                g->gc->ToGrey(array);
            break;
        case obj_int32:
            if (remain) {
                memmove((int*)slots1 + index + 1, (int*)slots1 + index, remain * elemsize);
            }
            err = slotIntVal(c, &ival);
            if (err)
                return err;
            ((int32*)slots1)[index] = ival;
            break;
        case obj_int16:
            if (remain) {
                memmove((short*)slots1 + index + 1, (short*)slots1 + index, remain * elemsize);
            }
            err = slotIntVal(c, &ival);
            if (err)
                return err;
            ((int16*)slots1)[index] = ival;
            break;
        case obj_int8:
            if (remain) {
                memmove((char*)slots1 + index + 1, (char*)slots1 + index, remain * elemsize);
            }
            err = slotIntVal(c, &ival);
            if (err)
                return err;
            ((int8*)slots1)[index] = ival;
            break;
        case obj_char:
            if (remain) {
                memmove((char*)slots1 + index + 1, (char*)slots1 + index, remain * elemsize);
            }
            if (NotChar(c))
                return errWrongType;
            ((char*)slots1)[index] = slotRawInt(c);
            break;
        case obj_symbol:
            if (remain) {
                memmove((int*)slots1 + index + 1, (int*)slots1 + index, remain * elemsize);
            }
            if (NotSym(c))
                return errWrongType;
            ((PyrSymbol**)slots1)[index] = slotRawSymbol(c);
            break;
        case obj_float:
            if (remain) {
                memmove((float*)slots1 + index + 1, (float*)slots1 + index, remain * elemsize);
            }
            err = slotDoubleVal(c, &fval);
            if (err)
                return err;
            ((float*)slots1)[index] = fval;
            break;
        case obj_double:
            if (remain) {
                memmove((double*)slots1 + index + 1, (double*)slots1 + index, remain * elemsize);
            }
            err = slotDoubleVal(c, &fval);
            if (err)
                return err;
            ((double*)slots1)[index] = fval;
            break;
        }
    }
    return errNone;
}


int prArrayFill(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots;
    PyrObject* array;
    PyrSymbol* sym;
    int i;
    int format;
    int err, ival;
    double fval;


    a = g->sp - 1;
    b = g->sp;

    array = slotRawObject(a);
    format = slotRawObject(a)->obj_format;
    /*int tag = gFormatElemTag[format];
    if (tag > 0) {
        if (GetTag(b) != tag) return errWrongType;
    } else if (tag == 0) {
        if (NotFloat(b)) return errWrongType;
    } // else format is obj_slot, any tag is acceptable*/
    slots = array->slots;
    switch (format) {
    case obj_slot:
        if (array->IsImmutable())
            return errImmutableObject;
        for (i = 0; i < array->size; ++i) {
            slotCopy(&slots[i], b);
        }
        g->gc->GCWrite(array, b);
        break;
    case obj_int32:
        err = slotIntVal(b, &ival);
        if (err)
            return err;
        for (i = 0; i < array->size; ++i) {
            ((int32*)slots)[i] = ival;
        }
        break;
    case obj_int16:
        err = slotIntVal(b, &ival);
        if (err)
            return err;
        for (i = 0; i < array->size; ++i) {
            ((int16*)slots)[i] = ival;
        }
        break;
    case obj_int8:
        err = slotIntVal(b, &ival);
        if (err)
            return err;
        for (i = 0; i < array->size; ++i) {
            ((int8*)slots)[i] = ival;
        }
        break;
    case obj_char:
        if (NotChar(b))
            return errWrongType;
        ival = slotRawInt(b);
        for (i = 0; i < array->size; ++i) {
            ((char*)slots)[i] = ival;
        }
        break;
    case obj_symbol:
        if (NotSym(b))
            return errWrongType;
        sym = slotRawSymbol(b);
        for (i = 0; i < array->size; ++i) {
            ((PyrSymbol**)slots)[i] = sym;
        }
        break;
    case obj_float:
        err = slotDoubleVal(b, &fval);
        if (err)
            return err;
        for (i = 0; i < array->size; ++i) {
            ((float*)slots)[i] = fval;
        }
        break;
    case obj_double:
        err = slotDoubleVal(b, &fval);
        if (err)
            return err;
        for (i = 0; i < array->size; ++i) {
            ((double*)slots)[i] = fval;
        }
        break;
    }
    return errNone;
}

int prArrayPop(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *slots;
    PyrObject* array;
    int z;
    int format;
    PyrSymbol* sym;

    a = g->sp;

    array = slotRawObject(a);
    if (array->IsImmutable())
        return errImmutableObject;
    if (array->size > 0) {
        format = array->obj_format;
        slots = array->slots;
        switch (format) {
        case obj_slot:
            slotCopy(a, &slots[--array->size]);
            break;
        case obj_int32:
            z = ((int32*)slots)[--array->size];
            SetInt(a, z);
            break;
        case obj_int16:
            z = ((int16*)slots)[--array->size];
            SetInt(a, z);
            break;
        case obj_int8:
            z = ((int8*)slots)[--array->size];
            SetInt(a, z);
            break;
        case obj_char:
            z = ((char*)slots)[--array->size];
            SetChar(a, z);
            break;
        case obj_symbol:
            sym = ((PyrSymbol**)slots)[--array->size];
            SetSymbol(a, sym);
            break;
        case obj_float:
            SetFloat(a, ((float*)slots)[--array->size]);
            break;
        case obj_double:
            SetFloat(a, slotRawFloat(&slots[--array->size]));
            break;
        }
    } else {
        slotCopy(a, &o_nil);
    }
    return errNone;
}


int prArrayExtend(struct VMGlobals* g, int numArgsPushed) {
    int numbytes, elemsize, format;
    int err;

    PyrSlot* a = g->sp - 2; // array
    PyrSlot* b = g->sp - 1; // size
    PyrSlot* c = g->sp; // filler item


    if (NotInt(b))
        return errWrongType;
    PyrObject* aobj = slotRawObject(a);
    if (slotRawInt(b) <= aobj->size) {
        aobj->size = slotRawInt(b);
        return errNone;
    }

    format = aobj->obj_format;
    if (slotRawInt(b) > MAXINDEXSIZE(aobj) || aobj->IsImmutable()) {
        elemsize = gFormatElemSize[format];
        numbytes = slotRawInt(b) * elemsize;

        PyrObject* obj = g->gc->New(numbytes, 0, format, true);
        obj->classptr = aobj->classptr;
        obj->size = aobj->size;
        memcpy(obj->slots, aobj->slots, aobj->size * elemsize);
        aobj = obj;
        SetRaw(a, aobj);
    }


    int fillSize = slotRawInt(b) - aobj->size;
    int32 ival;
    float fval;
    double dval;

    PyrSlot* slots = aobj->slots;
    switch (format) {
    case obj_slot:
        fillSlots(slots + aobj->size, fillSize, c);
        g->gc->GCWrite(aobj, c);
        break;
    case obj_int32: {
        int32* ptr = (int32*)slots + aobj->size;
        err = slotIntVal(c, &ival);
        if (err)
            return err;
        for (int i = 0; i < fillSize; ++i)
            ptr[i] = ival;
    } break;
    case obj_int16: {
        int16* ptr = (int16*)slots + aobj->size;
        err = slotIntVal(c, &ival);
        if (err)
            return err;
        for (int i = 0; i < fillSize; ++i)
            ptr[i] = ival;
    } break;
    case obj_int8: {
        int8* ptr = (int8*)slots + aobj->size;
        err = slotIntVal(c, &ival);
        if (err)
            return err;
        for (int i = 0; i < fillSize; ++i)
            ptr[i] = ival;
    } break;
    case obj_char: {
        char* ptr = (char*)slots + aobj->size;
        if (NotChar(c))
            return errWrongType;
        ival = slotRawChar(c);
        for (int i = 0; i < fillSize; ++i)
            ptr[i] = ival;
    } break;
    case obj_symbol: {
        PyrSymbol** ptr = (PyrSymbol**)slots + aobj->size;
        if (NotSym(c))
            return errWrongType;
        PyrSymbol* sym = slotRawSymbol(c);
        for (int i = 0; i < fillSize; ++i)
            ptr[i] = sym;
    } break;
    case obj_float: {
        float* ptr = (float*)slots + aobj->size;
        err = slotFloatVal(c, &fval);
        for (int i = 0; i < fillSize; ++i)
            ptr[i] = fval;
    } break;
    case obj_double: {
        double* ptr = (double*)slots + aobj->size;
        err = slotDoubleVal(c, &dval);
        for (int i = 0; i < fillSize; ++i)
            ptr[i] = dval;
    } break;
    }

    aobj->size = slotRawInt(b);
    return errNone;
}

int prArrayGrow(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    PyrObject *obj, *aobj;
    int numbytes, elemsize, format;

    a = g->sp - 1;
    b = g->sp;

    if (NotInt(b))
        return errWrongType;
    if (slotRawInt(b) <= 0)
        return errNone;
    aobj = slotRawObject(a);

    if (aobj->size + slotRawInt(b) <= MAXINDEXSIZE(aobj))
        return errNone;

    format = aobj->obj_format;
    elemsize = gFormatElemSize[format];
    numbytes = ((aobj->size + slotRawInt(b)) * elemsize);

    obj = g->gc->New(numbytes, 0, format, true);
    obj->classptr = aobj->classptr;
    obj->size = aobj->size;
    memcpy(obj->slots, aobj->slots, aobj->size * elemsize);
    SetRaw(a, obj);

    return errNone;
}

int prArrayGrowClear(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    PyrObject *obj, *aobj;
    int numbytes, elemsize, format;

    a = g->sp - 1;
    b = g->sp;

    if (NotInt(b))
        return errWrongType;
    if (slotRawInt(b) <= 0)
        return errNone;
    aobj = slotRawObject(a);

    if (aobj->size + slotRawInt(b) <= MAXINDEXSIZE(aobj) && aobj->IsMutable()) {
        obj = aobj;
    } else {
        format = aobj->obj_format;
        elemsize = gFormatElemSize[format];
        numbytes = ((aobj->size + slotRawInt(b)) * elemsize);

        obj = g->gc->New(numbytes, 0, format, true);
        obj->classptr = aobj->classptr;
        memcpy(obj->slots, aobj->slots, aobj->size * elemsize);
    }

    if (obj->obj_format == obj_slot) {
        nilSlots(obj->slots + aobj->size, slotRawInt(b));
    } else {
        memset((char*)(obj->slots) + aobj->size * gFormatElemSize[format], 0, slotRawInt(b) * gFormatElemSize[format]);
    }
    obj->size = aobj->size + slotRawInt(b);
    SetRaw(a, obj);

    return errNone;
}

int prArrayCat(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    PyrObject *obj, *aobj, *bobj;
    int elemsize, size;
    int numbytes, format;

    a = g->sp - 1;
    b = g->sp;

    if (NotObj(b) || slotRawObject(a)->classptr != slotRawObject(b)->classptr)
        return errWrongType;
    aobj = slotRawObject(a);
    bobj = slotRawObject(b);
    size = aobj->size + bobj->size;
    format = aobj->obj_format;
    assert(aobj->obj_format == bobj->obj_format);
    elemsize = gFormatElemSize[format];
    numbytes = (size * elemsize);

    obj = g->gc->New(numbytes, 0, format, true);
    obj->classptr = aobj->classptr;
    obj->size = size;
    memcpy(obj->slots, aobj->slots, aobj->size * elemsize);
    memcpy((char*)obj->slots + aobj->size * elemsize, bobj->slots, bobj->size * elemsize);
    SetObject(a, obj);

    return errNone;
}


int prArrayAddAll(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    PyrObject *obj, *aobj;
    int elemsize, newindexedsize, newsizebytes, asize, bsize;
    int format;

    a = g->sp - 1;
    b = g->sp;

    if (NotObj(b) || slotRawObject(a)->classptr != slotRawObject(b)->classptr)
        return errWrongType;
    aobj = slotRawObject(a);

    format = aobj->obj_format;
    elemsize = gFormatElemSize[format];
    asize = aobj->size;
    bsize = slotRawObject(b)->size;
    newindexedsize = asize + bsize;
    newsizebytes = newindexedsize * elemsize;

    if (newindexedsize > MAXINDEXSIZE(aobj) || aobj->IsImmutable()) {
        obj = g->gc->New(newsizebytes, 0, format, true);
        obj->classptr = aobj->classptr;
        memcpy(obj->slots, aobj->slots, asize * elemsize);
        SetObject(a, obj);
    } else {
        obj = aobj;
        if (format == obj_slot && !g->gc->ObjIsGrey(obj))
            g->gc->ToGrey(obj);
    }
    obj->size = newindexedsize;
    memcpy((char*)obj->slots + asize * elemsize, slotRawObject(b)->slots, bsize * elemsize);
    return errNone;
}


int prArrayOverwrite(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    PyrObject *obj, *aobj;
    int err, elemsize, newindexedsize, newsizebytes, pos, asize, bsize;
    int format;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp; // pos

    if (NotObj(b) || slotRawObject(a)->classptr != slotRawObject(b)->classptr)
        return errWrongType;
    err = slotIntVal(c, &pos);
    if (err)
        return errWrongType;
    if (pos < 0 || pos > slotRawObject(a)->size)
        return errIndexOutOfRange;

    aobj = slotRawObject(a);
    format = aobj->obj_format;
    elemsize = gFormatElemSize[format];
    asize = aobj->size;
    bsize = slotRawObject(b)->size;
    newindexedsize = pos + bsize;
    newindexedsize = sc_max(asize, newindexedsize);
    newsizebytes = newindexedsize * elemsize;

    if (newindexedsize > MAXINDEXSIZE(aobj) || aobj->IsImmutable()) {
        obj = g->gc->New(newsizebytes, 0, format, true);
        obj->classptr = aobj->classptr;
        memcpy(obj->slots, aobj->slots, asize * elemsize);
        SetObject(a, obj);
    } else {
        obj = aobj;
        if (format == obj_slot && !g->gc->ObjIsGrey(obj))
            g->gc->ToGrey(obj);
    }
    obj->size = newindexedsize;
    memcpy((char*)(obj->slots) + pos * elemsize, slotRawObject(b)->slots, bsize * elemsize);

    return errNone;
}

int prArrayReverse(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *slots1, *slots2;
    PyrObject *obj1, *obj2;
    int i, j, size;

    a = g->sp;
    obj1 = slotRawObject(a);
    size = obj1->size;
    obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
    slots1 = obj1->slots;
    slots2 = obj2->slots;
    for (i = 0, j = size - 1; i < size; ++i, --j) {
        slotCopy(&slots2[j], &slots1[i]);
    }
    obj2->size = size;
    SetRaw(a, obj2);
    return errNone;
}

int prArrayScramble(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *slots1, *slots2, temp;
    PyrObject *obj1, *obj2;
    int i, j, k, m, size;

    a = g->sp;
    obj1 = slotRawObject(a);
    size = obj1->size;
    obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
    slots1 = obj1->slots;
    slots2 = obj2->slots;
    memcpy(slots2, slots1, size * sizeof(PyrSlot));
    if (size > 1) {
        k = size;
        for (i = 0, m = k; i < k - 1; ++i, --m) {
            j = i + g->rgen->irand(m);
            slotCopy(&temp, &slots2[i]);
            slotCopy(&slots2[i], &slots2[j]);
            slotCopy(&slots2[j], &temp);
        }
    }
    obj2->size = size;
    SetRaw(a, obj2);
    return errNone;
}

int prArrayRotate(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots;
    PyrObject *obj1, *obj2;
    int i, j, n, size;

    a = g->sp - 1;
    b = g->sp;
    if (NotInt(b))
        return errWrongType;

    obj1 = slotRawObject(a);
    size = obj1->size;
    n = sc_mod((int)slotRawInt(b), (int)size);
    slots = obj1->slots;
    obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
    for (i = 0, j = n; i < size; ++i) {
        slotCopy(&obj2->slots[j], &slots[i]);
        if (++j >= size)
            j = 0;
    }
    obj2->size = size;
    SetRaw(a, obj2);
    return errNone;
}

int prArrayDupEach(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots1, *slots2;
    PyrObject *obj1, *obj2;
    int i, j, k, m, n, size;

    a = g->sp - 1;
    b = g->sp;
    if (NotInt(b))
        return errWrongType;

    obj1 = slotRawObject(a);
    n = slotRawInt(b);
    m = obj1->size;
    size = m * n;
    obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
    slots1 = obj1->slots;
    slots2 = obj2->slots;
    for (i = 0, j = 0; i < m; ++i) {
        for (k = 0; k < n; ++k, ++j) {
            slotCopy(&slots2[j], &slots1[i]);
        }
    }
    obj2->size = size;
    SetRaw(a, obj2);
    return errNone;
}

int prArrayMirror(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *slots;
    PyrObject *obj1, *obj2;
    int i, j, k, size;

    a = g->sp;

    obj1 = slotRawObject(a);
    slots = obj1->slots;
    size = std::max(obj1->size * 2 - 1, 0);
    obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
    obj2->size = size;
    // copy first part of list
    memcpy(obj2->slots, slots, obj1->size * sizeof(PyrSlot));
    // copy second part
    k = size / 2;
    for (i = 0, j = size - 1; i < k; ++i, --j) {
        slotCopy(&obj2->slots[j], &slots[i]);
    }
    SetRaw(a, obj2);
    return errNone;
}

int prArrayMirror1(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *slots;
    PyrObject *obj1, *obj2;
    int i, j, k, size;

    a = g->sp;

    obj1 = slotRawObject(a);
    slots = obj1->slots;
    size = std::max(obj1->size * 2 - 2, obj1->size);
    obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
    obj2->size = size;
    memcpy(obj2->slots, slots, obj1->size * sizeof(PyrSlot));
    // copy second part
    k = size / 2;
    for (i = 1, j = size - 1; i < k; ++i, --j) {
        slotCopy(&obj2->slots[j], &slots[i]);
    }
    SetRaw(a, obj2);
    return errNone;
}

int prArrayMirror2(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *slots;
    PyrObject *obj1, *obj2;
    int i, j, k, size;

    a = g->sp;

    obj1 = slotRawObject(a);
    slots = obj1->slots;
    size = obj1->size * 2;
    obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
    obj2->size = size;
    // copy first part of list
    memcpy(obj2->slots, slots, obj1->size * sizeof(PyrSlot));
    // copy second part
    k = size / 2;
    for (i = 0, j = size - 1; i < k; ++i, --j) {
        slotCopy(&obj2->slots[j], &slots[i]);
    }
    SetRaw(a, obj2);
    return errNone;
}


int prArrayExtendWrap(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots;
    PyrObject *obj1, *obj2;
    int i, j, m, size;

    a = g->sp - 1;
    b = g->sp;
    if (NotInt(b))
        return errWrongType;

    size = slotRawInt(b);
    if (size < 0)
        return errFailed;

    obj1 = slotRawObject(a);

    if (obj1->size > 0) {
        obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
        obj2->size = size;
        slots = obj2->slots;
        // copy first part of list
        memcpy(slots, obj1->slots, sc_min(size, obj1->size) * sizeof(PyrSlot));
        if (size > obj1->size) {
            // copy second part
            m = obj1->size;
            for (i = 0, j = m; j < size; ++i, ++j) {
                slotCopy(&slots[j], &slots[i]);
            }
        }
    } else {
        obj2 = instantiateObject(g->gc, obj1->classptr, size, true, true);
    }
    SetRaw(a, obj2);
    return errNone;
}

int prArrayExtendFold(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots;
    PyrObject *obj1, *obj2;
    int i, j, m, size;

    a = g->sp - 1;
    b = g->sp;
    if (NotInt(b))
        return errWrongType;

    size = slotRawInt(b);
    if (size < 0)
        return errFailed;

    obj1 = slotRawObject(a);
    if (obj1->size > 0) {
        obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
        obj2->size = size;
        slots = obj2->slots;
        // copy first part of list
        memcpy(slots, obj1->slots, sc_min(size, obj1->size) * sizeof(PyrSlot));
        if (size > obj1->size) {
            // copy second part
            m = obj1->size;
            for (i = 0, j = m; j < size; ++i, ++j) {
                slotCopy(&slots[j], &slots[sc_fold(j, 0, m - 1)]);
            }
        }
    } else {
        obj2 = instantiateObject(g->gc, obj1->classptr, size, true, true);
    }
    SetRaw(a, obj2);
    return errNone;
}

int prArrayExtendLast(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots, last;
    PyrObject *obj1, *obj2;
    int i, j, m, size;

    a = g->sp - 1;
    b = g->sp;
    if (NotInt(b))
        return errWrongType;

    size = slotRawInt(b);
    if (size < 0)
        return errFailed;

    obj1 = slotRawObject(a);
    if (obj1->size > 0) {
        obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
        obj2->size = size;
        slots = obj2->slots;
        // copy first part of list
        memcpy(slots, obj1->slots, sc_min(size, obj1->size) * sizeof(PyrSlot));
        if (size > obj1->size) {
            // copy second part
            m = obj1->size;
            slotCopy(&last, &slots[m - 1]);
            for (i = 0, j = m; j < size; ++i, ++j)
                slotCopy(&slots[j], &last);
        }
    } else {
        obj2 = instantiateObject(g->gc, obj1->classptr, size, true, true);
    }
    SetRaw(a, obj2);
    return errNone;
}

int prArrayPermute(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots1, *slots2, temp;
    PyrObject *obj1, *obj2;
    int i, j, m, z, size;

    a = g->sp - 1;
    b = g->sp;
    if (NotInt(b))
        return errWrongType;

    obj1 = slotRawObject(a);
    size = obj1->size;
    obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
    obj2->size = size;
    slots1 = obj1->slots;
    slots2 = obj2->slots;
    memcpy(slots2, slots1, size * sizeof(PyrSlot));
    z = slotRawInt(b);
    for (i = 0, m = size; i < size - 1; ++i, --m) {
        j = i + sc_mod((int)z, (int)(size - i));
        z = sc_div(z, size - i);
        slotCopy(&temp, &slots2[i]);
        slotCopy(&slots2[i], &slots2[j]);
        slotCopy(&slots2[j], &temp);
    }
    SetRaw(a, obj2);
    return errNone;
}


int prArrayAllTuples(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots1, *slots2, *slots3, *slotToCopy;
    PyrObject *obj1, *obj2, *obj3;

    a = g->sp - 1;
    b = g->sp;
    if (NotInt(b))
        return errWrongType;
    int maxSize = slotRawInt(b);

    obj1 = slotRawObject(a);
    slots1 = obj1->slots;
    int newSize = 1;
    int tupSize = obj1->size;
    for (int i = 0; i < tupSize; ++i) {
        if (isKindOfSlot(slots1 + i, class_array)) {
            newSize *= slotRawObject(&slots1[i])->size;
        }
    }
    if (newSize > maxSize)
        newSize = maxSize;

    obj2 = instantiateObject(g->gc, obj1->classptr, newSize, false, true);
    slots2 = obj2->slots;
    SetObject(b, obj2); // store reference on stack, so both source and destination objects can be reached by the gc

    for (int i = 0; i < newSize; ++i) {
        int k = i;
        obj3 = instantiateObject(g->gc, obj1->classptr, tupSize, false, true);
        slots3 = obj3->slots;
        for (int j = tupSize - 1; j >= 0; --j) {
            if (isKindOfSlot(slots1 + j, class_array)) {
                PyrObject* obj4 = slotRawObject(&slots1[j]);
                slotToCopy = &obj4->slots[k % obj4->size];
                slotCopy(&slots3[j], slotToCopy);
                g->gc->GCWrite(obj3, slotToCopy);
                k /= obj4->size;
            } else {
                slotToCopy = &slots1[j];
                slotCopy(&slots3[j], slotToCopy);
                g->gc->GCWrite(obj3, slotToCopy);
            }
        }
        obj3->size = tupSize;
        SetObject(obj2->slots + i, obj3);
        g->gc->GCWriteNew(obj2, obj3);
        obj2->size++;
    }
    SetRaw(a, obj2);
    return errNone;
}

int prArrayPyramid(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots;
    PyrObject *obj1, *obj2;
    int i, j, k, n, m, numslots, x;

    a = g->sp - 1;
    b = g->sp;
    if (NotInt(b))
        return errWrongType;

    obj1 = slotRawObject(a);
    slots = obj1->slots;
    m = sc_clip(slotRawInt(b), 1, 10);
    x = numslots = obj1->size;
    switch (m) {
    case 1:
        n = (x * x + x) / 2;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = 0; j <= i; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    case 2:
        n = (x * x + x) / 2;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = numslots - 1 - i; j <= numslots - 1; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    case 3:
        n = (x * x + x) / 2;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = 0; j <= numslots - 1 - i; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    case 4:
        n = (x * x + x) / 2;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = i; j <= numslots - 1; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    case 5:
        n = x * x;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = 0; j <= i; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        for (i = 0; i < numslots - 1; ++i) {
            for (j = 0; j <= numslots - 2 - i; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    case 6:
        n = x * x;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = numslots - 1 - i; j <= numslots - 1; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        for (i = 0; i < numslots - 1; ++i) {
            for (j = i + 1; j <= numslots - 1; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    case 7:
        n = x * x + x - 1;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = 0; j <= numslots - 1 - i; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        for (i = 1; i < numslots; ++i) {
            for (j = 0; j <= i; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    case 8:
        n = x * x + x - 1;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = i; j <= numslots - 1; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        for (i = 1; i < numslots; ++i) {
            for (j = numslots - 1 - i; j <= numslots - 1; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    case 9:
        n = x * x;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = 0; j <= i; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        for (i = 0; i < numslots - 1; ++i) {
            for (j = i + 1; j <= numslots - 1; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    case 10:
        n = x * x;
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = 0, k = 0; i < numslots; ++i) {
            for (j = numslots - 1 - i; j <= numslots - 1; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        for (i = 0; i < numslots - 1; ++i) {
            for (j = 0; j <= numslots - 2 - i; ++j, ++k) {
                slotCopy(&obj2->slots[k], &slots[j]);
            }
        }
        obj2->size = k;
        break;
    }
    SetRaw(a, obj2);
    return errNone;
}

int prArraySlide(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c, *slots;
    PyrObject *obj1, *obj2;
    int h, i, j, k, numslots, numwin;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;
    if (NotInt(b))
        return errWrongType;
    if (NotInt(c))
        return errWrongType;

    obj1 = slotRawObject(a);
    slots = obj1->slots;
    int m = slotRawInt(b);
    int n = slotRawInt(c);
    if (n <= 0)
        return errFailed;

    numwin = (obj1->size + n - m) / n;
    numslots = numwin * m;
    obj2 = instantiateObject(g->gc, obj1->classptr, numslots, false, true);
    for (i = h = k = 0; i < numwin; ++i, h += n) {
        for (j = h; j < m + h; ++j)
            slotCopy(&obj2->slots[k++], &slots[j]);
    }
    obj2->size = k;
    SetRaw(a, obj2);
    return errNone;
}

int prArrayLace(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots, *slot;
    PyrObject *obj1, *obj2, *obj3;
    int i, j, k, n, m, numLists, len;

    a = g->sp - 1;
    b = g->sp;
    obj1 = slotRawObject(a);
    slots = obj1->slots;
    numLists = obj1->size;

    if (IsNil(b)) {
        // If length argument is unspecified, compute length as the multiple of
        // the length of the shortest sub-array and the number of sub-arrays.
        for (j = 0; j < numLists; ++j) {
            slot = slots + j;
            if (isKindOfSlot(slot, class_list)) {
                obj2 = slotRawObject(slot);
                slot = &obj2->slots[0];
            }
            if (isKindOfSlot(slot, class_array)) {
                len = slotRawObject(slot)->size;
                if (j == 0 || n > len) {
                    n = len;
                }
            } else {
                // If not an array we assume a max per-element length of one.
                n = 1;
                break;
            }
        }
        n = n * numLists;

    } else if (IsInt(b)) {
        n = slotRawInt(b);
    } else {
        return errWrongType;
    }


    n = sc_max(1, n);
    if (obj1->size > 0) {
        obj2 = instantiateObject(g->gc, obj1->classptr, n, false, true);
        for (i = j = k = 0; i < n; ++i) {
            if (IsObj(&slots[k])) {
                obj3 = slotRawObject(&slots[k]);
                if (isKindOf(obj3, class_list)) {
                    obj3 = slotRawObject(&obj3->slots[0]); // get the list's array
                }
                if (obj3 && isKindOf(obj3, class_array)) {
                    if (obj3->size > 0) {
                        m = j % obj3->size;
                        slotCopy(&obj2->slots[i], &obj3->slots[m]);
                    } else {
                        SetNil(&obj2->slots[i]);
                    }
                } else {
                    slotCopy(&obj2->slots[i], &slots[k]);
                }
            } else {
                slotCopy(&obj2->slots[i], &slots[k]);
            }
            k = (k + 1) % obj1->size;
            if (k == 0)
                j++;
        }
        obj2->size = n;
    } else {
        if (isKindOf(obj1, class_array)) {
            obj2 = newPyrArray(g->gc, 0, 0, true);
        } else {
            obj2 = instantiateObject(g->gc, obj1->classptr, n, true, true);
            obj2->size = n;
        }
    }
    SetRaw(a, obj2);
    return errNone;
}

int prArrayContainsSeqColl(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *slot, *endptr;
    PyrObject* obj;
    int size;

    a = g->sp;
    obj = slotRawObject(a);
    size = obj->size;
    slot = obj->slots - 1;
    endptr = slot + size;
    while (slot < endptr) {
        ++slot;
        if (IsObj(slot)) {
            if (isKindOf(slotRawObject(slot), class_sequenceable_collection)) {
                SetTrue(a);
                return errNone;
            }
        }
    }
    SetFalse(a);
    return errNone;
}

int prArrayNormalizeSum(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *slots2;
    PyrObject *obj1, *obj2;
    int i, size, err;
    double w, sum, rsum;

    a = g->sp;
    obj1 = slotRawObject(a);
    size = obj1->size;
    obj2 = instantiateObject(g->gc, obj1->classptr, size, false, true);
    slots2 = obj2->slots;
    sum = 0.0;
    for (i = 0; i < size; ++i) {
        err = getIndexedDouble(obj1, i, &w);
        if (err)
            return err;
        sum += w;
        SetFloat(&slots2[i], w);
    }
    rsum = 1. / sum;
    for (i = 0; i < size; ++i) {
        double d = slotRawFloat(&slots2[i]);
        SetRaw(&slots2[i], d * rsum);
    }
    obj2->size = size;
    SetRaw(a, obj2);
    return errNone;
}


int prArrayWIndex(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *slots;
    PyrObject* obj;
    int i, j, size, err;
    double r, w, sum;

    a = g->sp;

    sum = 0.0;
    r = g->rgen->frand();
    obj = slotRawObject(a);
    size = obj->size;
    j = size - 1;
    slots = obj->slots;
    for (i = 0; i < size; ++i) {
        err = getIndexedDouble(obj, i, &w);
        if (err)
            return err;
        sum += w;
        if (sum >= r) {
            j = i;
            break;
        }
    }
    SetInt(a, j);
    return errNone;
}


enum {
    shape_Step,
    shape_Linear,
    shape_Exponential,
    shape_Sine,
    shape_Welch,
    shape_Curve,
    shape_Squared,
    shape_Cubed,
    shape_Hold
};

enum { kEnv_initLevel, kEnv_numStages, kEnv_releaseNode, kEnv_loopNode };

int prArrayEnvAt(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot* a = g->sp - 1;
    PyrSlot* b = g->sp;

    PyrObject* env = slotRawObject(a);
    PyrSlot* slots = env->slots;

    // Env:asArray always gives at least 8 array elements
    if (env->size < 8)
        return errFailed;

    double time;
    int err = slotDoubleVal(b, &time);
    if (err)
        return err;

    double begLevel;
    err = slotDoubleVal(slots + kEnv_initLevel, &begLevel);
    if (err)
        return err;

    int numStages;
    err = slotIntVal(slots + kEnv_numStages, &numStages);
    if (err)
        return err;

    double begTime = 0.;
    double endTime = 0.;

    for (int i = 0; i < numStages; ++i) {
        double dur, endLevel;

        slots += 4;

        err = slotDoubleVal(slots + 0, &endLevel);
        if (err)
            return err;
        err = slotDoubleVal(slots + 1, &dur);
        if (err)
            return err;

        endTime += dur;

        // post("%d   %g   %g %g   %g   %g\n", i, time, begTime, endTime, dur, endLevel);

        if (time < endTime) {
            int shape;
            double curve;

            err = slotIntVal(slots + 2, &shape);
            if (err)
                return err;

            double level;
            double pos = (time - begTime) / dur;

            // post("    shape %d   pos %g\n", shape, pos);
            switch (shape) {
            case shape_Step:
                level = endLevel;
                break;
            case shape_Hold:
                level = begLevel;
                break;
            case shape_Linear:
            default:
                level = pos * (endLevel - begLevel) + begLevel;
                break;
            case shape_Exponential:
                level = begLevel * pow(endLevel / begLevel, pos);
                break;
            case shape_Sine:
                level = begLevel + (endLevel - begLevel) * (-cos(pi * pos) * 0.5 + 0.5);
                break;
            case shape_Welch: {
                if (begLevel < endLevel)
                    level = begLevel + (endLevel - begLevel) * sin(pi2 * pos);
                else
                    level = endLevel - (endLevel - begLevel) * sin(pi2 - pi2 * pos);
                break;
            }
            case shape_Curve:
                err = slotDoubleVal(slots + 3, &curve);
                if (err)
                    return err;

                if (fabs(curve) < 0.0001) {
                    level = pos * (endLevel - begLevel) + begLevel;
                } else {
                    double denom = 1. - exp(curve);
                    double numer = 1. - exp(pos * curve);
                    level = begLevel + (endLevel - begLevel) * (numer / denom);
                }
                break;
            case shape_Squared: {
                double sqrtBegLevel = sqrt(begLevel);
                double sqrtEndLevel = sqrt(endLevel);
                double sqrtLevel = pos * (sqrtEndLevel - sqrtBegLevel) + sqrtBegLevel;
                level = sqrtLevel * sqrtLevel;
                break;
            }
            case shape_Cubed: {
                double cbrtBegLevel = pow(begLevel, 0.3333333);
                double cbrtEndLevel = pow(endLevel, 0.3333333);
                double cbrtLevel = pos * (cbrtEndLevel - cbrtBegLevel) + cbrtBegLevel;
                level = cbrtLevel * cbrtLevel * cbrtLevel;
                break;
            }
            }
            SetFloat(a, level);
            return errNone;
        }

        begTime = endTime;
        begLevel = endLevel;
    }

    SetFloat(a, begLevel);

    return errNone;
}


int prArrayIndexOfGreaterThan(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *slots;
    PyrObject* obj;
    int i, size, err;
    double s, w;

    a = g->sp - 1;
    b = g->sp;

    obj = slotRawObject(a);

    size = obj->size;
    slots = obj->slots;

    err = slotDoubleVal(b, &s);
    if (err)
        return err;

    for (i = 0; i < size; ++i) {
        err = getIndexedDouble(obj, i, &w);
        if (err)
            return err;

        if (w > s) {
            SetInt(a, i);
            return errNone;
        }
    }

    SetNil(a);
    return errNone;
}


int prArrayUnlace(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c, *slots, *slots2, *slots3;
    PyrObject *obj1, *obj2, *obj3;
    int i, j, k, clump, numLists, size, size3, err;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    obj1 = slotRawObject(a);
    slots = obj1->slots;
    size = obj1->size;

    err = slotIntVal(b, &numLists);
    if (err)
        return err;

    err = slotIntVal(c, &clump);
    if (err)
        return err;

    obj2 = instantiateObject(g->gc, obj1->classptr, numLists, false, true);
    slots2 = obj2->slots;

    SetObject(b, obj2); // store reference on stack, so both source and destination objects can be reached by the gc

    size3 = size / numLists;
    size3 = size3 - (size3 % clump);
    if (size3 < 1)
        return errFailed;

    for (i = 0; i < numLists; ++i) {
        obj3 = instantiateObject(g->gc, obj1->classptr, size3, false, true);
        obj3->size = size3;
        slots3 = obj3->slots;
        for (j = 0; j < size3; j += clump) {
            for (k = 0; k < clump; ++k) {
                slotCopy(&slots3[j + k], &slots[i * clump + k + j * numLists]);
            }
        }
        SetObject(slots2 + i, obj3);
        g->gc->GCWriteNew(obj2, obj3); // we know obj3 is white so we can use GCWriteNew
        obj2->size++;
    }

    SetRaw(a, obj2);

    return errNone;
}

template <typename T>
int levenshteinDistanceImpl(
    PyrSlot* result, const PyrObject* a, const PyrObject* b,
    std::function<bool(const T&, const T&)> compareFunc = [](const T& a, const T& b) { return a == b; }) {
    size_t distance = levenshteinDistance<T>()(reinterpret_cast<const T*>(a->slots), a->size,
                                               reinterpret_cast<const T*>(b->slots), b->size, compareFunc);
    SetInt(result, static_cast<int>(distance));
    return errNone;
}

int arrayLevenshteinDistance(PyrSlot* result, const PyrObject* thisArray, const PyrObject* thatArray) {
    // if they're both the same format we can compare them..
    if (thisArray->obj_format == thatArray->obj_format) {
        switch (thisArray->obj_format) {
        // if they're raw arrays, compare as such
        case obj_char:
            return levenshteinDistanceImpl<char>(result, thisArray, thatArray);
        case obj_double:
            return levenshteinDistanceImpl<double>(result, thisArray, thatArray);
        case obj_float:
            return levenshteinDistanceImpl<float>(result, thisArray, thatArray);
        case obj_int16:
            return levenshteinDistanceImpl<int16>(result, thisArray, thatArray);
        case obj_int32:
            return levenshteinDistanceImpl<int32>(result, thisArray, thatArray);
        case obj_int8:
            return levenshteinDistanceImpl<int8>(result, thisArray, thatArray);
        case obj_symbol:
            // pass them in as slots, compare as raw symbols
            return levenshteinDistanceImpl<PyrSlot>(
                result, thisArray, thatArray,
                [](const PyrSlot& a, const PyrSlot& b) { return slotRawSymbol(&a) == slotRawSymbol(&b); });
        case obj_slot:
            // if it is slotted, compare identities
            // this will handle polymorphic arrays in a predictable way
            return levenshteinDistanceImpl<PyrSlot>(result, thisArray, thatArray,
                                                    [](const PyrSlot& a, const PyrSlot& b) { return SlotEq(&a, &b); });
        default:
            return errWrongType;
        }
    } else {
        return errWrongType;
    }
}

int prArrayLevenshteinDistance(struct VMGlobals* g, int numArgsPushed) {
    auto* slotThatArray = g->sp;
    auto* slotThisArray = g->sp - 1;

    if (NotObj(slotThisArray) || NotObj(slotThatArray))
        return errWrongType;

    auto* objThisArray = slotRawObject(slotThisArray);
    auto* objThatArray = slotRawObject(slotThatArray);

    if (!(slotRawInt(&objThisArray->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    if (!(slotRawInt(&objThatArray->classptr->classFlags) & classHasIndexableInstances))
        return errNotAnIndexableObject;

    return arrayLevenshteinDistance(slotThisArray, objThisArray, objThatArray);
}

struct IsRectangularResult {
    // an int with three special values
    // -1: depth first searching the array has reached something isn't an array (nesting terminator),
    //      this is a 'truthy' result.
    // -2: the array is not rectangular, a valid false result.
    // -3: one of the children was a Collection, but not an arrayed collection. An error.
    [[nodiscard]] static constexpr IsRectangularResult make_size(int sz) {
        assert(sz >= 0);
        return IsRectangularResult(sz);
    }
    [[nodiscard]] static constexpr IsRectangularResult make_nesting_terminator() { return IsRectangularResult(-1); }
    [[nodiscard]] static constexpr IsRectangularResult make_false_result() { return IsRectangularResult(-2); }
    [[nodiscard]] static constexpr IsRectangularResult make_type_error() { return IsRectangularResult(-3); }
    [[nodiscard]] constexpr bool is_valid() const noexcept {
        return m_value >= 0 || m_value == make_nesting_terminator();
    }
    [[nodiscard]] constexpr bool is_type_error() const noexcept { return m_value == make_type_error(); }

    // implicitly converts to int.
    constexpr operator int() const noexcept { return m_value; }

private:
    constexpr explicit IsRectangularResult(int i) noexcept: m_value(i) {}
    int m_value;
};

// Does a depth first check of the size and shape.
// On left most path, inserts the size.
inline IsRectangularResult is_rectangular(const PyrSlot* a, std::vector<IsRectangularResult>& expected_size,
                                          uint32_t depth, bool is_left_most) {
    // no Lists, only derived classes of ArrayedCollection!
    if (isKindOfSlot(a, class_sequenceable_collection) && !isKindOfSlot(a, class_arrayed_collection)) {
        return IsRectangularResult::make_type_error();
    }

    // 'a' is not an array
    if (!IsObj(a) || !isKindOfSlot(a, class_arrayed_collection)) {
        // has not reached this depth before
        if (expected_size.size() < depth + 1) {
            if (!is_left_most)
                return IsRectangularResult::make_false_result(); // did not match
            // ensure other nested array always terminates at this depth
            expected_size.push_back(IsRectangularResult::make_nesting_terminator());
        }
        return expected_size[depth] != IsRectangularResult::make_nesting_terminator()
            ? IsRectangularResult::make_false_result()
            : IsRectangularResult::make_nesting_terminator();
    }

    // 'a' is an array.

    const PyrObject* obj = slotRawObject(a);
    const int size = obj->size;

    if (expected_size.size() < depth + 1) {
        if (!is_left_most)
            return IsRectangularResult::make_false_result();
        expected_size.push_back(IsRectangularResult::make_size(size));
    }

    // mismatch
    if (size != expected_size[depth])
        return IsRectangularResult::make_false_result();


    // raw arrays are functors over exactly one type, which is always a c++ scalar type
    // (https://en.cppreference.com/w/cpp/types/is_scalar)
    // therefore the size can be returned without checking
    if (isKindOf(obj, class_rawarray))
        return IsRectangularResult::make_size(size);

    // early return for no children
    if (size <= 0)
        return IsRectangularResult::make_size(0);
    // iterator over the children
    const PyrSlot* item_at_index = obj->slots;

    // left most path, inserts expected_size
    const auto first_sz = is_rectangular(item_at_index, expected_size, depth + 1, is_left_most);
    if (!first_sz.is_valid())
        return first_sz;

    for (int i = 1; i < size; ++i) {
        const auto this_sz = is_rectangular(item_at_index + i, expected_size, depth + 1, false);
        if (!this_sz.is_valid())
            return this_sz;
        else if (this_sz != first_sz)
            return IsRectangularResult::make_false_result();
        else
            continue;
    }
    return IsRectangularResult::make_size(size);
}

int prArrayIsRectangular(struct VMGlobals* g, int numArgsPushed) {
    // checks array's sub arrays all have the same size, recurs on those sub arrays to check them
    PyrSlot* array = g->sp;
    if (!isKindOfSlot(array, class_arrayed_collection))
        return errWrongType;

    std::vector<IsRectangularResult> expected_shape;
    const auto r = is_rectangular(array, expected_shape, 0, true);
    if (r.is_type_error()) {
        post("Subarray was not an instance of ArrayedCollection.\n");
        SetNil(array);
        return errWrongType;
    } else {
        SetBool(array, r.is_valid());
        return errNone;
    }
}

// Convert Array-of-Arrays (receiver) -> std::vector<std::vector<double>>
static int readDoubleMatrixFromReceiver(VMGlobals* g, PyrSlot* recv, std::vector<std::vector<double>>& out) {
    if (!isKindOfSlot(recv, class_array))
        return errWrongType;
    PyrObject* rows = slotRawObject(recv);
    const int J = rows->size;
    if (J <= 0) {
        out.clear();
        return errNone;
    }

    // ensure rectangular & numeric
    int Wref = -1;
    out.assign(J, {});
    for (int i = 0; i < J; ++i) {
        PyrSlot* rowSlot = rows->slots + i;
        if (!isKindOfSlot(rowSlot, class_array))
            return errWrongType;
        PyrObject* rowObj = slotRawObject(rowSlot);
        const int W = rowObj->size;
        if (Wref < 0)
            Wref = W;
        else if (W != Wref)
            return errIndexOutOfRange;

        out[i].resize(W);
        for (int j = 0; j < W; ++j) {
            double v;
            if (slotDoubleVal(rowObj->slots + j, &v))
                return errWrongType;
            out[i][j] = v;
        }
    }
    return errNone;
}

// _ArrayHungarianSolve(receiver = 2D matrix) -> [ cost: Float, assignment: IntArray ]
static int prArrayHungarianSolve(VMGlobals* g, int numArgsPushed) {
    PyrSlot* a = g->sp; // receiver (Array of Arrays), same convention as other Array prims

    std::vector<std::vector<double>> C;
    int err = readDoubleMatrixFromReceiver(g, a, C);
    if (err)
        return err;

    // Empty -> return [0, []]
    if (C.empty() || C[0].empty()) {
        PyrObject* out = newPyrArray(g->gc, 2, 0, true);
        SetObject(a, out);
        SetFloat(out->slots + 0, 0.0);
        out->size = 1;

        PyrObject* asg = newPyrArray(g->gc, 0, 0, true);
        SetObject(out->slots + 1, asg);
        out->size = 2;
        g->gc->GCWriteNew(out, asg);
        asg->size = 0;
        return errNone;
    }

    auto res = schungarian::solve<double>(C);
    const double cost = (double)res.first;
    const std::vector<int>& match = res.second;

    // Build result: [ cost, assignment ]
    PyrObject* out = newPyrArray(g->gc, 2, 0, true);
    SetObject(a, out); // put return value on the receiver slot
    SetFloat(out->slots + 0, cost);
    out->size = 1;

    PyrObject* asg = newPyrArray(g->gc, (int)match.size(), 0, true);
    SetObject(out->slots + 1, asg);
    out->size = 2;
    g->gc->GCWriteNew(out, asg);

    for (int i = 0; i < (int)match.size(); ++i) {
        SetInt(asg->slots + i, match[i]);
    }
    asg->size = (int)match.size();

    return errNone;
}

void initArrayPrimitives() {
    int base, index;

    base = nextPrimitiveIndex();
    index = 0;

    definePrimitive(base, index++, "_BasicSize", basicSize, 1, 0);
    definePrimitive(base, index++, "_BasicMaxSize", basicMaxSize, 1, 0);

    definePrimitive(base, index++, "_BasicSwap", basicSwap, 3, 0);
    definePrimitive(base, index++, "_BasicAt", basicAt, 2, 0);
    definePrimitive(base, index++, "_BasicRemoveAt", basicRemoveAt, 2, 0);
    definePrimitive(base, index++, "_BasicTakeAt", basicTakeAt, 2, 0);
    definePrimitive(base, index++, "_BasicClipAt", basicClipAt, 2, 0);
    definePrimitive(base, index++, "_BasicWrapAt", basicWrapAt, 2, 0);
    definePrimitive(base, index++, "_BasicFoldAt", basicFoldAt, 2, 0);
    definePrimitive(base, index++, "_BasicPut", basicPut, 3, 0);
    definePrimitive(base, index++, "_BasicClipPut", basicClipPut, 3, 0);
    definePrimitive(base, index++, "_BasicWrapPut", basicWrapPut, 3, 0);
    definePrimitive(base, index++, "_BasicFoldPut", basicFoldPut, 3, 0);

    definePrimitive(base, index++, "_ArrayExtend", prArrayExtend, 3, 0);
    definePrimitive(base, index++, "_ArrayGrow", prArrayGrow, 2, 0);
    definePrimitive(base, index++, "_ArrayGrowClear", prArrayGrowClear, 2, 0);
    definePrimitive(base, index++, "_ArrayAdd", prArrayAdd, 2, 0);
    definePrimitive(base, index++, "_ArrayInsert", prArrayInsert, 3, 0);
    definePrimitive(base, index++, "_ArrayFill", prArrayFill, 2, 0);
    definePrimitive(base, index++, "_ArrayPop", prArrayPop, 1, 0);
    definePrimitive(base, index++, "_ArrayCat", prArrayCat, 2, 0);
    definePrimitive(base, index++, "_ArrayPutEach", prArrayPutEach, 3, 0);
    definePrimitive(base, index++, "_ArrayAddAll", prArrayAddAll, 2, 0);
    definePrimitive(base, index++, "_ArrayPutSeries", prArrayPutSeries, 5, 0);
    definePrimitive(base, index++, "_ArrayOverwrite", prArrayOverwrite, 3, 0);
    definePrimitive(base, index++, "_ArrayIndexOf", prArrayIndexOf, 2, 0);

    definePrimitive(base, index++, "_ArrayNormalizeSum", prArrayNormalizeSum, 1, 0);
    definePrimitive(base, index++, "_ArrayWIndex", prArrayWIndex, 1, 0);
    definePrimitive(base, index++, "_ArrayReverse", prArrayReverse, 1, 0);
    definePrimitive(base, index++, "_ArrayScramble", prArrayScramble, 1, 0);
    definePrimitive(base, index++, "_ArrayMirror", prArrayMirror, 1, 0);
    definePrimitive(base, index++, "_ArrayMirror1", prArrayMirror1, 1, 0);
    definePrimitive(base, index++, "_ArrayMirror2", prArrayMirror2, 1, 0);
    definePrimitive(base, index++, "_ArrayRotate", prArrayRotate, 2, 0);
    definePrimitive(base, index++, "_ArrayPermute", prArrayPermute, 2, 0);
    definePrimitive(base, index++, "_ArrayAllTuples", prArrayAllTuples, 2, 0);
    definePrimitive(base, index++, "_ArrayPyramid", prArrayPyramid, 2, 0);
    definePrimitive(base, index++, "_ArrayRotate", prArrayRotate, 2, 0);
    definePrimitive(base, index++, "_ArrayExtendWrap", prArrayExtendWrap, 2, 0);
    definePrimitive(base, index++, "_ArrayExtendFold", prArrayExtendFold, 2, 0);
    definePrimitive(base, index++, "_ArrayExtendLast", prArrayExtendLast, 2, 0);
    definePrimitive(base, index++, "_ArrayLace", prArrayLace, 2, 0);
    definePrimitive(base, index++, "_ArrayDupEach", prArrayDupEach, 2, 0);
    definePrimitive(base, index++, "_ArraySlide", prArraySlide, 3, 0);
    definePrimitive(base, index++, "_ArrayContainsSeqColl", prArrayContainsSeqColl, 1, 0);

    definePrimitive(base, index++, "_ArrayEnvAt", prArrayEnvAt, 2, 0);
    definePrimitive(base, index++, "_ArrayIndexOfGreaterThan", prArrayIndexOfGreaterThan, 2, 0);
    definePrimitive(base, index++, "_ArrayUnlace", prArrayUnlace, 3, 0);

    definePrimitive(base, index++, "_ArrayLevenshteinDistance", prArrayLevenshteinDistance, 2, 0);
    definePrimitive(base, index++, "_ArrayIsRectangular", prArrayIsRectangular, 1, 0);

    definePrimitive(base, index++, "_ArrayHungarianSolve", prArrayHungarianSolve, 1, 0);
}
